package cn.schoolwow.quickdao.module.entity.flow.scan;

import cn.schoolwow.quickdao.annotation.TableName;
import cn.schoolwow.quickdao.domain.QuickDAOConfig;
import cn.schoolwow.quickdao.domain.entity.Entity;
import cn.schoolwow.quickdao.module.entity.flow.common.ShouldIgnoreClassFlow;
import cn.schoolwow.quickdao.util.StringUtil;
import cn.schoolwow.quickflow.domain.FlowContext;
import cn.schoolwow.quickflow.flow.BusinessFlow;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ScanPackageClassFlow implements BusinessFlow {
    private Logger logger = LoggerFactory.getLogger(ScanPackageClassFlow.class);

    @Override
    public void executeBusinessFlow(FlowContext flowContext) throws Exception {
        QuickDAOConfig quickDAOConfig = flowContext.checkInstanceData(QuickDAOConfig.class);

        //扫描实体类包
        for (String packageName : quickDAOConfig.entityOption.packageNameMap.keySet()) {
            List<Class> packageClassList = scanPackageClassByPackageName(packageName, flowContext);
            for (Class packageClass : packageClassList) {
                Entity entity = new Entity();
                if(null!=quickDAOConfig.entityOption.entityListener){
                    entity.tableName = quickDAOConfig.entityOption.entityListener.getTableName(packageClass);
                }
                if(null==entity.tableName&&null!=packageClass.getDeclaredAnnotation(TableName.class)){
                    entity.tableName = ((TableName) packageClass.getDeclaredAnnotation(TableName.class)).value();
                }
                if(null==entity.tableName&&(packageName.length() + packageClass.getSimpleName().length() + 1) == packageClass.getName().length()){
                    entity.tableName = quickDAOConfig.entityOption.packageNameMap.get(packageName) + StringUtil.camel2Underline(packageClass.getSimpleName());
                }
                if(null==entity.tableName){
                    String prefix = packageClass.getName().substring(packageName.length() + 1, packageClass.getName().lastIndexOf(".")).replace(".", "_");
                    entity.tableName = quickDAOConfig.entityOption.packageNameMap.get(packageName) + prefix + "@" + StringUtil.camel2Underline(packageClass.getSimpleName());
                }
                entity.clazz = packageClass;
                quickDAOConfig.databaseContext.entityMap.put(packageClass.getName(), entity);
            }
        }
    }

    @Override
    public String name() {
        return "扫描实体类包";
    }

    /**
     * 扫描指定包下的类
     *
     * @param packageName  包名
     */
    private List<Class> scanPackageClassByPackageName(String packageName, FlowContext flowContext) {
        String packageNamePath = packageName.replace(".", "/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL url = classLoader.getResource(packageNamePath);
        if (url == null) {
            logger.warn("包路径不存在,包名:{}", packageName);
            return new ArrayList<>();
        }
        List<String> classNameList = new ArrayList<>();
        try {
            switch (url.getProtocol()) {
                case "file": {
                    File file = new File(url.getFile());
                    //TODO 对于有空格或者中文路径会无法识别
                    if (!file.isDirectory()) {
                        throw new IllegalArgumentException("包名不是合法的文件夹!" + url.getFile());
                    }
                    String indexOfString = packageName.replace(".", "/");
                    Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
                        @Override
                        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                            File f = file.toFile();
                            if (f.getName().endsWith(".class")) {
                                String path = f.getAbsolutePath().replace("\\", "/");
                                int startIndex = path.indexOf(indexOfString);
                                String className = path.substring(startIndex, path.length() - 6).replace("/", ".");
                                classNameList.add(className);
                            }
                            return FileVisitResult.CONTINUE;
                        }
                    });
                }
                break;
                case "jar": {
                    JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                    if (null != jarURLConnection) {
                        JarFile jarFile = jarURLConnection.getJarFile();
                        if (null != jarFile) {
                            Enumeration<JarEntry> jarEntries = jarFile.entries();
                            while (jarEntries.hasMoreElements()) {
                                JarEntry jarEntry = jarEntries.nextElement();
                                String jarEntryName = jarEntry.getName();
                                if (jarEntryName.contains(packageNamePath) && jarEntryName.endsWith(".class")) { //是否是类,是类进行加载
                                    String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                    classNameList.add(className);
                                }
                            }
                        }
                    }
                }
                break;
            }
            List<Class> classList = new ArrayList<>(classNameList.size());
            for (String className : classNameList) {
                Class clazz = classLoader.loadClass(className);
                boolean shouldIgnoreClass = (boolean) flowContext.startFlow(new ShouldIgnoreClassFlow())
                        .printTrace(false)
                        .putTemporaryData("clazz", clazz)
                        .execute()
                        .checkData("shouldIgnoreClass");
                if (shouldIgnoreClass) {
                    continue;
                }
                classList.add(clazz);
            }
            return classList;
        } catch (Exception e) {
            throw new RuntimeException("读取实体类信息时发生异常!", e);
        }
    }

}
